package com.tvunetworks.device.extra.util;


import java.net.InetAddress;
import java.nio.ByteBuffer;

/**
 * @author liam
 */
public abstract class IPUtils {

    private static final int IPV4_PART_COUNT = 4;
    private static final int IPV6_PART_COUNT = 8;

    /**
     * 判断是不是公网ip
     * @param ip ip
     * @return true/false
     */
    public static boolean isPublicIP(String ip) {
        InetAddress address = getInetAddress(ip);
        if (address == null) {
            return false;
        }
        boolean nonPublicIP = address.isSiteLocalAddress()
                || address.isAnyLocalAddress()
                || address.isLoopbackAddress()
                || address.isLinkLocalAddress()
                || address.isMulticastAddress();
        return !nonPublicIP;
    }

    /**
     * 判断是不是私有ip
     * @param ip ip
     * @return true/false
     */
    public static boolean isPrivateIP(String ip) {
        InetAddress address = getInetAddress(ip);
        return address != null && address.isSiteLocalAddress();
    }

    /**
     * @param ip ip
     * @return true/false
     */
    public static boolean isReservedIP(String ip) {
        try {
            String[] parts = ip.split("\\.");
            int i1 = Integer.parseInt(parts[0]);
            int i2 = Integer.parseInt(parts[1]);
            return (i1 >= 224 && i1 <= 239) || (i1 == 169 && i2 == 254) || (i1 == 127);
        } catch (Exception ignore) {
            return false;
        }
    }

    private static InetAddress getInetAddress(String ip) {
        try {
            byte[] bytes = toIPBytes(ip);
            return bytes == null ? null : InetAddress.getByAddress(bytes);
        } catch (Exception exception) {
            return null;
        }
    }

    private static byte[] toIPBytes(String ip) {
        try {
            if (ip.contains(".")) {
                return textToNumericFormatV4(ip);
            } else if (ip.contains(":")) {
                return textToNumericFormatV6(ip);
            }
        } catch (Exception ignore) {

        }
        return null;
    }

    private static byte parseOctet(String ipPart) {
        // Note: we already verified that this string contains only hex digits.
        int octet = Integer.parseInt(ipPart);
        // Disallow leading zeroes, because no clear standard exists on
        // whether these should be interpreted as decimal or octal.
        if (octet > 255 || (ipPart.startsWith("0") && ipPart.length() > 1)) {
            throw new NumberFormatException();
        }
        return (byte) octet;
    }

    private static byte[] textToNumericFormatV4(String ipString) {
        String[] address = ipString.split("\\.", IPV4_PART_COUNT + 1);
        if (address.length != IPV4_PART_COUNT) {
            return null;
        }
        byte[] bytes = new byte[IPV4_PART_COUNT];
        try {
            for (int i = 0; i < bytes.length; i++) {
                bytes[i] = parseOctet(address[i]);
            }
        } catch (NumberFormatException ex) {
            return null;
        }
        return bytes;
    }

    private static byte[] textToNumericFormatV6(String ipString) {
        // An address can have [2..8] colons, and N colons make N+1 parts.
        String[] parts = ipString.split(":", IPV6_PART_COUNT + 2);
        if (parts.length < 3 || parts.length > IPV6_PART_COUNT + 1) {
            return null;
        }
        // Disregarding the endpoints, find "::" with nothing in between.
        // This indicates that a run of zeroes has been skipped.
        int skipIndex = -1;
        for (int i = 1; i < parts.length - 1; i++) {
            if (parts[i].length() == 0) {
                if (skipIndex >= 0) {
                    return null;  // Can't have more than one ::
                }
                skipIndex = i;
            }
        }
        int partsHi;  // Number of parts to copy from above/before the "::"
        int partsLo;  // Number of parts to copy from below/after the "::"
        if (skipIndex >= 0) {
            // If we found a "::", then check if it also covers the endpoints.
            partsHi = skipIndex;
            partsLo = parts.length - skipIndex - 1;
            if (parts[0].length() == 0 && --partsHi != 0) {
                return null;  // ^: requires ^::
            }
            if (parts[parts.length - 1].length() == 0 && --partsLo != 0) {
                return null;  // :$ requires ::$
            }
        } else {
            // Otherwise, allocate the entire address to partsHi.  The endpoints
            // could still be empty, but parseHextet() will check for that.
            partsHi = parts.length;
            partsLo = 0;
        }
        // If we found a ::, then we must have skipped at least one part.
        // Otherwise, we must have exactly the right number of parts.
        int partsSkipped = IPV6_PART_COUNT - (partsHi + partsLo);
        if (!(skipIndex >= 0 ? partsSkipped >= 1 : partsSkipped == 0)) {
            return null;
        }
        // Now parse the hextets into a byte array.
        ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT);
        try {
            for (int i = 0; i < partsHi; i++) {
                rawBytes.putShort(parseHextet(parts[i]));
            }
            for (int i = 0; i < partsSkipped; i++) {
                rawBytes.putShort((short) 0);
            }
            for (int i = partsLo; i > 0; i--) {
                rawBytes.putShort(parseHextet(parts[parts.length - i]));
            }
        } catch (NumberFormatException ex) {
            return null;
        }
        return rawBytes.array();
    }

    private static short parseHextet(String ipPart) {
        // Note: we already verified that this string contains only hex digits.
        int hextet = Integer.parseInt(ipPart, 16);
        if (hextet > 0xffff) {
            throw new NumberFormatException();
        }
        return (short) hextet;
    }


    /**
     * @param ip ip
     * @return true/false
     */
    public static boolean isIP(String ip) {
        InetAddress address = getInetAddress(ip);
        return address != null;
    }


}

